package nels4561;

import java.util.ArrayList;
import java.util.Set;

import spacewar2.objects.Asteroid;
import spacewar2.objects.Base;
import spacewar2.objects.Beacon;
import spacewar2.objects.Ship;
import spacewar2.objects.SpacewarObject;
import spacewar2.simulator.Toroidal2DPhysics;
import spacewar2.utilities.Position;

/**
 * A 2D array of GridCells. Used by A* for navigation/referencing for each Ship
 * 
 * @author Michael & Thomas
 */
public class Grid 
{
	
	/**
	 * The number of rows in the Grid
	 */
	public int rows;
	/**
	 * The number of columns in the Grid
	 */
	public int columns;
	/**
	 * The current Ship this Grid is assigned to
	 */
	Ship currentShip;
	/**
	 * The actual 2D Array of GridCells
	 */
	public GridCell[][] grid;

	/**
	 * The Grid constructor
	 * 
	 * @param space: spacewar environment
	 * @param ship: current ship
	 */
	public Grid(Toroidal2DPhysics space, Ship ship) 
	{
		calculateGridDimensions(space);
		grid = new GridCell[rows][columns];

		for (int i = 0; i < rows; i++) 
		{
			for (int j = 0; j < columns; j++) 
			{
				grid[i][j] = new GridCell(i, j);
			}
		}
		currentShip = ship;
		//detectAsteroids(space);
		detectObjects(space);
	}

	
	/**
	 * Calculate the number of rows and columns
	 * based off the space we are in
	 * 
	 * @param space: spacewar environment
	 */
	public void calculateGridDimensions(Toroidal2DPhysics space)
	{
		//function variables
		int spaceHeight = space.getHeight();
		int spaceWidth = space.getWidth();

		rows = (spaceHeight) / (GridCell.HEIGHT);
		columns = (spaceWidth) / (GridCell.WIDTH);
	}

	/**
	 * Populate our Grid by instantiating each of its GridCells
	 * and giving each one a heuristic value based off the target
	 * 
	 * @param space: spacewar environment
	 * @param target: current target of the agent
	 */
	public void populateGrid(Toroidal2DPhysics space, SpacewarObject target) 
	{
		//for each cell set the h value to the Euclidean distance from cell to target
		for (int i = 0; i < rows; i++) 
		{
			for (int j = 0; j < columns; j++) 
			{
				GridCell cell = grid[i][j];
				double hDist = space.findShortestDistance(cell._location, target.getPosition());
				cell.h = hDist;
			}
		}
	}

	/**
	 * Detect any asteroids and assign them to one or more GridCells
	 * 
	 * @param space: spacewar environment
	 */
	public void detectAsteroids(Toroidal2DPhysics space) 
	{
		Set<Asteroid> asteroids = space.getAsteroids();
		for (Asteroid asteroid : asteroids) {
			double x = asteroid.getPosition().getX();
			double y = asteroid.getPosition().getY();

			int row = (int) (y / GridCell.HEIGHT);
			int col = (int) (x / GridCell.HEIGHT);

			if (row > rows - 1)
				row = 0;
			if (col > columns - 1)
				col = 0;

			GridCell cell = grid[row][col];
			cell.containsAsteroid = true;

			cell.Asteroid = asteroid;
			if (asteroid.isMineable())
				cell.containsMineableAsteroid = true;

			ArrayList<GridCell> neighbors = cell.getNeighborsWithinRadius(this, 1);
			for (GridCell neighbor : neighbors)
			{
				if (space.findShortestDistance(asteroid.getPosition(),
						neighbor._location) < (asteroid.getRadius() + GridCell.CELLDIAGONAL)) 
				{
					neighbor.containsAsteroid = true;

					neighbor.Asteroid = asteroid;
					if (asteroid.isMineable())
						neighbor.containsMineableAsteroid = true;
				}
			}
		}
	}
	/**
	 * Detect any Asteroids, Beacons, or Bases and assign them to one or more GridCells
	 * 
	 * @param space
	 */
	public void detectObjects(Toroidal2DPhysics space) 
	{
		Set<Asteroid> asteroids = space.getAsteroids();
		Set<Beacon> beacons = space.getBeacons();
		Set<Base> bases = space.getBases();

		//set object variable on cells that have asteroids, mineable and not
		for (Asteroid asteroid : asteroids) 
		{
			double x = asteroid.getPosition().getX();
			double y = asteroid.getPosition().getY();

			int row = (int) (y / GridCell.HEIGHT);
			int col = (int) (x / GridCell.HEIGHT);

			//if going off the map, go to other side of the map
			if (row > rows - 1)
				row = 0;
			if (col > columns - 1)
				col = 0;

			GridCell cell = grid[row][col];
			cell.object = asteroid;

			if (!asteroid.isMineable()) {
				cell.avoid = true;
			}
			
			ArrayList<GridCell> neighbors = cell.getNeighborsWithinRadius(this, 2);
			for (GridCell neighbor : neighbors) 
			{
				if (Math.abs(space.findShortestDistance(asteroid.getPosition(),
						neighbor._location)) < (asteroid.getRadius() * 2 + GridCell.CELLDIAGONAL)) 
				{
					if (!asteroid.isMineable()) 
					{
						neighbor.avoid = true;
					}
					neighbor.object = asteroid;
				}
			}
		}
		
		//set object variable on cells that have beacons
		for (Beacon beacon : beacons) 
		{
			double x = beacon.getPosition().getX();
			double y = beacon.getPosition().getY();

			int row = (int) (y / GridCell.HEIGHT);
			int col = (int) (x / GridCell.HEIGHT);

			//if going off the map, go to other side of the map
			if (row > rows - 1)
				row = 0;
			if (col > columns - 1)
				col = 0;

			GridCell cell = grid[row][col];
			cell.object = beacon;
		}
		
		//set object variable on cells that have bases, friendly and hostile
		for (Base base : bases) 
		{
			double x = base.getPosition().getX();
			double y = base.getPosition().getY();

			int row = (int) (y / GridCell.HEIGHT);
			int col = (int) (x / GridCell.HEIGHT);

			//if going off the map, go to other side of the map
			if (row > rows - 1)
				row = 0;
			if (col > columns - 1)
				col = 0;

			GridCell cell = grid[row][col];
			cell.object = base;
			
			if(base.getTeamName() != this.currentShip.getTeamName())
				cell.avoid = true;
			else if(this.currentShip.getMoney() == 0)
				cell.avoid = true;
		}
	}

	/**
	 * Get the GridCell corresponding to the Position provided
	 * 
	 * @param space: spacewar environment
	 * @param pos: position that we are looking for the gridCell of
	 */
	public GridCell getGridCellFromPosition(Toroidal2DPhysics space, Position pos) 
	{
		//function variables
		double posX = (pos.getX() + space.getWidth()) % space.getWidth();
		double posY = (pos.getY() + space.getHeight()) % space.getHeight();

		int row = (int) (posY / GridCell.HEIGHT);
		int column = (int) (posX / GridCell.WIDTH);

		//if going off the map, go to other side of the map
		if (row > rows - 1)
			row = 0;
		if (column > columns - 1)
			column = 0;

		return grid[row][column];
	}

	/**
	 * Get the current GridCell our Ship is in.
	 */
	public GridCell getCurrentCell() 
	{
		//function variables
		double posX = currentShip.getPosition().getX();
		double posY = currentShip.getPosition().getY();

		int row = (int) (posY / GridCell.HEIGHT);
		int column = (int) (posX / GridCell.WIDTH);

		//if going off the map, go to other side of the map
		if (row > rows - 1)
			row = 0;
		if (column > columns - 1)
			column = 0;

		return grid[row][column];
	}
}
